(function() {
  init()

  function init() {
    window.$ = s => document.querySelector(s)
    window.$all = s => document.querySelectorAll(s)
    window.animate = animate
    HTMLElement.prototype.hide = hide
    HTMLElement.prototype.show = show
    HTMLElement.prototype.fadeIn = fadeIn
    HTMLElement.prototype.fadeOut = fadeOut
    HTMLElement.prototype.on = on
  }

  /**
   * @param {number} duration
   * @param {number} from
   * @param {number} to
   * @param {Funtion} onProgress
   * @param {Funtion} onFinish
   */
  function animate(duration, from, to, onProgress, onFinish) {
    return new Promise(resolve => {
      const s = to - from
      const v = s / duration
      const startTime = Date.now()
      let value = from
      onProgress(value)

      function _run() {
        const now = Date.now()
        const time = now - startTime
        const s = v * time

        if (time >= duration) {
          value = to
          onProgress(value)
          onFinish && onFinish()
          resolve()
          return
        }

        value = from + s
        onProgress(value)

        requestAnimationFrame(_run)
      }


      requestAnimationFrame(_run)
    })
  }

  function hide() {
    this.style.display = 'none'
  }

  function show() {
    this.style.display = 'block'
  }

  async function fadeIn(duration = 500) {
    this.show()
    await animate(duration, 0, 1, (val) => {
      this.style.opacity = val
    })
    this.style.removeProperty('opacity')
  }

  async function fadeOut(duration = 500) {
    await animate(duration, 1, 0, (val) => {
      this.style.opacity = val
    })
    this.hide()
    this.style.removeProperty('opacity')
  }

  /**
   * @param {keyof GlobalEventHandlersEventMap} event
   * @param {Function} cb
   */
  function on(event, cb) {
    this.onclick = cb
  }
})()
